#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SQL词法分析器
实现对输入的SQL语句进行识别，并切出目标词
"""

import re
from enum import Enum
from typing import List, Tuple, Optional, Dict
from ErrorCorrection import SmartErrorCorrector, ErrorInfo, ErrorType, ErrorSeverity


class TokenType(Enum):
    """单词符号类型枚举"""
    KEYWORD = 1      # 关键字：SQL语言的保留字，如SELECT、FROM、WHERE等
    IDENTIFIER = 2   # 标识符：表名、列名等用户自定义名称（个数不确定，作为一类）
    CONSTANT = 3     # 常数：各种类型的常数，如数字、字符串（个数不确定，按类型分类）
    OPERATOR = 4     # 运算符：如+、-、*、/、%、=、>、<等（个数确定，一符一类）
    DELIMITER = 5    # 界符：如,、;、(、)等分隔符（个数确定，一符一类）
    COMMENT = 6      # 注释：/* */、--等注释内容
    EOF = 7          # 文件结束符：标识输入结束


class Token:
    """
    单词符号类，用四元式表示
    四元式格式：(单词种类, 单词自身的值, 单词所在行号, 单词所在列号)
    
    说明：
    - 单词种类：表示单词种类，常用整数编码
    - 单词自身的值：是编译中其他阶段所需要的信息
    - 单词所在行列号：是单词所在程序的位置信息，如：行列号
    
    注意：
    - 如果一个种别只含一个单词符号，那么该单词符号的种别编码就代表它本身的值
    - 如果一个种别含有多个单词符号，那么还应给出该单词符号的自身值：
      标识符自身值是标识符自身的字符串；常数自身值是常数的二进制数值
    """
    
    def __init__(self, token_type: TokenType, value: str, line: int, column: int):
        self.type = token_type          # 单词种类：表示单词种类，常用整数编码
        self.value = value              # 单词自身的值：是编译中其他阶段所需要的信息
        self.line = line                # 单词所在行号：是单词所在程序的位置信息
        self.column = column            # 单词所在列号：是单词所在程序的位置信息
    
    def __repr__(self):
        return f"Output(type='{self.type.name}', value='{self.value}', line={self.line}, column={self.column})"
    
    def __str__(self):
        return self.__repr__()


class LexicalAnalyzer:
    """SQL词法分析器"""
    
    def __init__(self):
        # SQL关键字集合（不区分大小写）
        self.keywords = {
            'SELECT', 'FROM', 'WHERE', 'CREATE', 'TABLE', 'INSERT', 'INTO',
            'UPDATE', 'DELETE', 'DROP', 'ALTER', 'INDEX', 'PRIMARY', 'KEY',
            'FOREIGN', 'REFERENCES', 'NOT', 'NULL', 'UNIQUE', 'DEFAULT',
            'CHECK', 'CONSTRAINT', 'DATABASE', 'SCHEMA', 'VIEW', 'TRIGGER',
            'PROCEDURE', 'FUNCTION', 'RETURN', 'BEGIN', 'END', 'IF', 'ELSE',
            'WHILE', 'FOR', 'LOOP', 'BREAK', 'CONTINUE', 'CASE', 'WHEN',
            'THEN', 'ORDER', 'BY', 'GROUP', 'HAVING', 'DISTINCT', 'ALL',
            'UNION', 'INTERSECT', 'EXCEPT', 'JOIN', 'INNER', 'LEFT', 'RIGHT',
            'FULL', 'OUTER', 'CROSS', 'ON', 'AS', 'IN', 'EXISTS', 'BETWEEN',
            'LIKE', 'IS', 'AND', 'OR', 'XOR', 'VALUES', 'INT', 'VARCHAR',
            'SET', 'ASC', 'DESC', 'USE', 'LIMIT', 'GRANT', 'REVOKE', 'TO',
            'WITH', 'OPTION', 'PRIVILEGES', 'PUBLIC', 'ROLE', 'USER',
            # 数据库管理相关关键字
            'LIST', 'SHOW', 'DATABASES', 'SCHEMAS',
            # 用户管理相关关键字
            'USERS', 'PASSWORD', 'IDENTIFIED',
            # 聚合函数关键字
            'COUNT', 'SUM', 'AVG', 'MAX', 'MIN', 'STDDEV', 'VARIANCE'
        }
        
        # 智能纠错器
        self.error_corrector = SmartErrorCorrector()
        
        # 算术运算符
        self.arithmetic_operators = {'+', '-', '*', '/', '%'}
        
        # 比较运算符
        self.comparison_operators = {'=', '>', '<', '!=', '>=', '<=', '<>'}
        
        # 逻辑运算符（已包含在关键字中：AND, OR, NOT）
        
        # 界符
        self.delimiters = {',', ';', '.', '(', ')', '{', '}'}
        
        # 当前分析位置
        self.text = ""
        self.position = 0
        self.line = 1
        self.column = 1
        self.tokens = []
    
    def analyze(self, sql_text: str) -> List[Token]:
        """
        分析SQL文本，返回Token列表
        
        Args:
            sql_text: 输入的SQL语句
            
        Returns:
            List[Token]: 识别出的Token序列
        """
        self.text = sql_text
        self.position = 0
        self.line = 1
        self.column = 1
        self.tokens = []
        
        # 清空纠错器的错误记录
        self.error_corrector.clear_errors()
        
        while self.position < len(self.text):
            # 跳过空白字符
            if self._current_char().isspace():
                self._skip_whitespace()
                continue
            
            # 识别注释
            if self._peek_comment():
                self._read_comment()
                continue
            
            # 识别字符串常量
            if self._current_char() in ["'", '"']:
                self._read_string_constant()
                continue
            
            # 识别数字常量
            if self._current_char().isdigit():
                self._read_number_constant()
                continue
            
            # 识别比较运算符（需要先于单字符运算符识别）
            if self._peek_comparison_operator():
                self._read_comparison_operator()
                continue
            
            # 识别单字符运算符和界符
            if self._current_char() in self.arithmetic_operators or self._current_char() in self.delimiters:
                self._read_single_char_token()
                continue
            
            # 识别标识符或关键字
            if self._current_char().isalpha() or self._current_char() == '_':
                self._read_identifier_or_keyword()
                continue
            
            # 未识别的字符，报告错误并跳过
            self._handle_unrecognized_char()
        
        # 进行智能纠错分析
        self._perform_error_correction()
        
        # 添加文件结束符
        self.tokens.append(Token(TokenType.EOF, "EOF", self.line, self.column))
        return self.tokens
    
    def _current_char(self) -> str:
        """获取当前字符"""
        if self.position >= len(self.text):
            return '\0'
        return self.text[self.position]
    
    def _peek_char(self, offset: int = 1) -> str:
        """向前查看字符"""
        peek_pos = self.position + offset
        if peek_pos >= len(self.text):
            return '\0'
        return self.text[peek_pos]
    
    def _advance(self):
        """移动到下一个字符"""
        if self.position < len(self.text):
            if self.text[self.position] == '\n':
                self.line += 1
                self.column = 1
            else:
                self.column += 1
            self.position += 1
    
    def _skip_whitespace(self):
        """跳过空白字符"""
        while self.position < len(self.text) and self._current_char().isspace():
            self._advance()
    
    def _peek_comment(self) -> bool:
        """检查是否是注释开始"""
        current = self._current_char()
        next_char = self._peek_char()
        
        # 单行注释 --
        if current == '-' and next_char == '-':
            return True
        
        # 多行注释 /* */
        if current == '/' and next_char == '*':
            return True
        
        return False
    
    def _read_comment(self):
        """读取注释"""
        start_line = self.line
        start_column = self.column
        comment_text = ""
        
        current = self._current_char()
        next_char = self._peek_char()
        
        if current == '-' and next_char == '-':
            # 单行注释
            comment_text += current
            self._advance()
            comment_text += self._current_char()
            self._advance()
            
            while self.position < len(self.text) and self._current_char() != '\n':
                comment_text += self._current_char()
                self._advance()
        
        elif current == '/' and next_char == '*':
            # 多行注释
            comment_text += current
            self._advance()
            comment_text += self._current_char()
            self._advance()
            
            while self.position < len(self.text):
                if self._current_char() == '*' and self._peek_char() == '/':
                    comment_text += self._current_char()
                    self._advance()
                    comment_text += self._current_char()
                    self._advance()
                    break
                comment_text += self._current_char()
                self._advance()
        
        self.tokens.append(Token(TokenType.COMMENT, comment_text, start_line, start_column))
    
    def _read_string_constant(self):
        """读取字符串常量"""
        start_line = self.line
        start_column = self.column
        quote_char = self._current_char()  # ' 或 "
        string_value = ""
        
        self._advance()  # 跳过开始引号
        
        while self.position < len(self.text):
            current = self._current_char()
            
            if current == quote_char:
                # 检查是否是转义的引号
                if self._peek_char() == quote_char:
                    string_value += current
                    self._advance()
                    string_value += current
                    self._advance()
                else:
                    # 字符串结束
                    self._advance()  # 跳过结束引号
                    break
            else:
                string_value += current
                self._advance()
        
        self.tokens.append(Token(TokenType.CONSTANT, string_value, start_line, start_column))
    
    def _read_number_constant(self):
        """读取数字常量"""
        start_line = self.line
        start_column = self.column
        number_text = ""
        has_decimal = False
        
        while self.position < len(self.text):
            current = self._current_char()
            
            if current.isdigit():
                number_text += current
                self._advance()
            elif current == '.' and not has_decimal:
                # 小数点
                has_decimal = True
                number_text += current
                self._advance()
            else:
                break
        
        self.tokens.append(Token(TokenType.CONSTANT, number_text, start_line, start_column))
    
    def _peek_comparison_operator(self) -> bool:
        """检查是否是比较运算符"""
        current = self._current_char()
        next_char = self._peek_char()
        
        two_char_ops = {'!=', '>=', '<=', '<>'}
        two_char = current + next_char
        
        return two_char in two_char_ops or current in {'=', '>', '<'}
    
    def _read_comparison_operator(self):
        """读取比较运算符"""
        start_line = self.line
        start_column = self.column
        current = self._current_char()
        next_char = self._peek_char()
        
        two_char_ops = {'!=', '>=', '<=', '<>'}
        two_char = current + next_char
        
        if two_char in two_char_ops:
            operator = two_char
            self._advance()
            self._advance()
        else:
            operator = current
            self._advance()
        
        self.tokens.append(Token(TokenType.OPERATOR, operator, start_line, start_column))
    
    def _read_single_char_token(self):
        """读取单字符Token（运算符或界符）"""
        start_line = self.line
        start_column = self.column
        char = self._current_char()
        
        if char in self.arithmetic_operators:
            token_type = TokenType.OPERATOR
        else:
            token_type = TokenType.DELIMITER
        
        self.tokens.append(Token(token_type, char, start_line, start_column))
        self._advance()
    
    def _read_identifier_or_keyword(self):
        """读取标识符或关键字"""
        start_line = self.line
        start_column = self.column
        identifier = ""
        
        while (self.position < len(self.text) and 
               (self._current_char().isalnum() or self._current_char() == '_')):
            identifier += self._current_char()
            self._advance()
        
        # 判断是关键字还是标识符
        if identifier.upper() in self.keywords:
            token_type = TokenType.KEYWORD
        else:
            token_type = TokenType.IDENTIFIER
        
        self.tokens.append(Token(token_type, identifier, start_line, start_column))
    
    def _handle_unrecognized_char(self):
        """处理未识别的字符"""
        char = self._current_char()
        
        # 记录词法错误
        error = ErrorInfo(
            error_type=ErrorType.LEXICAL,
            severity=ErrorSeverity.ERROR,
            message=f"未识别的字符: '{char}'",
            line=self.line,
            column=self.column,
            token_value=char,
            suggestions=self._suggest_char_fix(char)
        )
        self.error_corrector.add_error(error)
        
        print(f"警告: 未识别的字符 '{char}' 在行 {self.line}, 列 {self.column}")
        self._advance()
    
    def _suggest_char_fix(self, char: str) -> List[str]:
        """为未识别字符建议修复方案"""
        suggestions = []
        
        # 常见的字符混淆
        char_fixes = {
            '（': '(',  # 中文括号
            '）': ')',
            '，': ',',  # 中文逗号
            '；': ';',  # 中文分号
            '"': '"',  # 中文双引号
            '"': '"',
            ''': "'",  # 中文单引号
            ''': "'",
        }
        
        if char in char_fixes:
            suggestions.append(f"将 '{char}' 替换为 '{char_fixes[char]}'")
        else:
            suggestions.append("删除此字符")
            suggestions.append("检查是否为输入错误")
        
        return suggestions
    
    def _perform_error_correction(self):
        """执行智能纠错分析"""
        # 分析词法错误
        lexical_errors = self.error_corrector.analyze_lexical_errors(self.text, self.tokens)
        for error in lexical_errors:
            self.error_corrector.add_error(error)
        
        # 检查标识符拼写（可能的关键字错误）
        for token in self.tokens:
            if token.type == TokenType.IDENTIFIER:
                spelling_error = self.error_corrector.check_spelling(
                    token.value, token.line, token.column
                )
                if spelling_error:
                    self.error_corrector.add_error(spelling_error)
    
    def get_error_report(self) -> str:
        """获取词法分析错误报告"""
        return self.error_corrector.format_error_report()
    
    def get_error_summary(self) -> Dict[str, int]:
        """获取错误统计摘要"""
        return self.error_corrector.get_error_summary()
    
    def has_errors(self) -> bool:
        """检查是否有错误"""
        return len(self.error_corrector.errors) > 0
    
    def print_tokens(self):
        """打印所有Token"""
        for token in self.tokens:
            print(token)


def main():
    """测试主函数"""
    # 创建词法分析器实例
    analyzer = LexicalAnalyzer()
    
    # 测试SQL语句
    test_sql = """/* test_sql_program */
        SELECT name, age
        FROM Students
        WHERE age > 20;"""
    
    print("输入的SQL语句:")
    print(test_sql)
    print("词法分析结果:")
    
    # 进行词法分析
    tokens = analyzer.analyze(test_sql)
    
    # 打印结果
    analyzer.print_tokens()


if __name__ == "__main__":
    main()
